home *** CD-ROM | disk | FTP | other *** search
- /* Functions for managing segments for applications or code resources.
- This file must be in the main segment of the program.
-
- 94/01/18 aih
- - THINK C uses two different jump table formats: one while debugging,
- the other for the final application. We determine which jump table
- format is being used and adjust access to the jump table accordingly.
-
- 94/01/04 aih
- - scans stack for segments in use, then only unloads segments
- that aren't in use
-
- 93/11/22 aih
- - adapted for new method of installing patches
- - added check for memory before loading code segment
-
- 93/03/26 AIH
- - Added patch to LoadSeg
-
- 93/03/22 AIH
- - Added functions for automatically loading and unloading segments,
- while keeping track of which segments are in use
-
- 93/03/19 AIH
- - Added function to unload all of the segments
-
- 93/03/10 AIH
- - Added special case for loading THINK C code segments
-
- 91/05/06 Ari Halberstadt (AIH)
- - Fixed potential problem with use of ResLoad (casting short to Boolean) */
-
- #include <Traps.h>
- #include "AsmLib.h"
- #include "LowMemLib.h"
- #include "MemoryLib.h"
- #include "PatchLib.h"
- #include "SegmentLib.h"
- #include "SortLib.h"
-
- /*----------------------------------------------------------------------------*/
- /* patch to loadseg */
- /*----------------------------------------------------------------------------*/
-
- /* For its own reasons, perhaps related to using FAR code, THINK C seems
- to patch the _LoadSeg trap. THINK C also makes code resources locked.
- Unfortunately, this prevents _LoadSeg from calling MoveHHi on segments,
- even if the low-memory global SegHiEnable is non-zero. Having code
- segments low in memory leads to severe heap fragmentation when pointers
- are allocated above the code segments. This patch will load the resource,
- move it to the top of the heap, and lock it there. This patch will also
- trigger an exception if the resource couldn't be loaded or if there isn't
- enough memory to load the resource. */
- static pascal void PatchLoadSeg(PatchType *patch, short seg)
- {
- Handle code;
-
- require(GetResLoad());
- SetResLoad(false);
- code = GetResource('CODE', seg);
- SetResLoad(true);
- FailNILRes(code);
- if (! *code) {
- /* Check that memory is available to load the resource. Segment
- unloading is disabled since unloading segments from within
- this patch crashes the mac eventually.
- (program_note: Why? Is the segment loader not reentrant? moving segments?) */
- Boolean unload = SegmentsUnloadEnable(false);
- Boolean available = MemAvailableNoCushion(SizeResource(code));
- SegmentsUnloadEnable(unload);
- if (! available)
- FailOSErr(memFullErr);
- }
- SetResAttrs(code, GetResAttrs(code) & ~resLocked);
- FailResError();
- LoadResource(code);
- FailResError();
- if (GetSegHiEnable()) {
- MoveHHi(code);
- HLock(code);
- }
- }
-
- pascal void LoadSegTrap(short seg) = _LoadSeg;
-
- /* load the segment */
- static void LoadSeg(short seg)
- {
- asm {
- move.w seg, -(sp)
- bra.s @1 /* call LoadSeg */
- bra.s @2 /* LoadSeg returns here */
- nop /* fill up two bytes */
- @1: LoadSegTrap
- @2:
- }
- }
-
- /*----------------------------------------------------------------------------*/
- /* segment unloading */
- /*----------------------------------------------------------------------------*/
-
- /* Unload all segments without return addresses on the stack. This
- assumes that register a6 is used as a stack frame pointer for all
- external functions and for all functions whose address is taken.
- Under THINK C 5.0.x, this is true if you use the force_frame option
- to force generation of a stack frame. Note that a stack frame is
- not generated for functions that take no parameters and have no
- local variables, but there are very very few functions like that
- in my code, and the ones that do exist probably have no impact
- on this segmentation strategy since they are called by a function
- with a stack frame in that segment.
-
- Based on information in IM-II, p60-62. */
-
- /*----------------------------------------------------------------------------*/
- /* jump table utilities */
- /*----------------------------------------------------------------------------*/
-
- #if defined(THINK_C) && __option(far_code)
- /* The jump table format is (probably) different when using far
- code, so we prevent compilation of this library. There are two
- solutions: either modify this library to support far code, or
- disable unloading of segments. */
- #error jump table access has not been tested for far code
- #endif
-
- #define JT_ENTRY_SIZE (8) /* size of a jump table entry */
- #define JT_ENTRY_HEADER_SIZE (2) /* the two bytes preceding the jump table
- entry's _LoadSeg or JMP instruction */
-
- /* We must determine whether we're debugging so that we can calculate the
- correct jump table offset (see the function SegmentJTOffset). We can tell
- that we're debugging since the type of the application's resource file won't
- be 'APPL' (the current resource file when this function is first called is
- assumed to be the application's resource file). */
- static short JTOffsetMultiplier(void)
- {
- static short multiplier = 0;
- FCBPBRec fcb;
- Str255 name;
- FInfo info;
-
- if (! multiplier) {
- multiplier = 1;
- #ifdef THINK_C
- fcb.ioCompletion = NULL;
- fcb.ioNamePtr = name;
- fcb.ioVRefNum = 0;
- fcb.ioRefNum = CurResFile();
- fcb.ioFCBIndx = 0;
- FailOSErr(PBGetFCBInfo(&fcb, false));
- FailOSErr(HGetFInfo(fcb.ioVRefNum, fcb.ioFCBParID, fcb.ioNamePtr, &info));
- if (info.fdType != 'APPL')
- multiplier = JT_ENTRY_SIZE;
- #endif /* THINK_C */
- }
- ensure(multiplier == 1 || multiplier == JT_ENTRY_SIZE);
- return(multiplier);
- }
-
- /* Given a pointer to a code resource, return the offset into the jump
- table of the segment's first routine. According to IM-II, the first two
- bytes of the code resource contain the offset into the jump table.
- However, In THINK C, when debugging (i.e., the application hasn't been
- built yet), the offset must be multiplied by 8; it is not clear to me
- why THINK C does this, but it seems to patch the segment loader. */
- static short SegmentJTOffset(Ptr code)
- {
- return(*(short *) code * JTOffsetMultiplier());
- }
-
- /* given a pointer to a code resource return the segment's first jump
- table entry */
- static Ptr JTEntry(Ptr code)
- {
- return(GetCurrentA5() + GetCurJTOffset() + SegmentJTOffset(code));
- }
-
- /* return segment number for a jump table entry */
- static short JTNumber(void *entry)
- {
- short *jmp = entry;
- short seg = 0;
-
- if (jmp[1] == ASM_JMP)
- seg = jmp[0]; /* loaded segment */
- else if (jmp[1] == ASM_MOVE && jmp[3] == _LoadSeg)
- seg = jmp[2]; /* unloaded segment */
- return(seg);
- }
-
- /*----------------------------------------------------------------------------*/
- /* segment unloading stuff */
- /*----------------------------------------------------------------------------*/
-
- /* addresses of loaded segments */
- typedef struct {
- Ptr start; /* start address of segment */
- Ptr end; /* one past last address of segment */
- Boolean active; /* true if segment is in use */
- } SegmentType;
-
- static Boolean gUnloadEnable; /* true enables segment unloading */
-
- /* comparison function for qksort */
- static int sort_compare(const void *a, const void *b)
- {
- if ((*(SegmentType *) a).start < (*(SegmentType *) b).start) return(-1);
- if ((*(SegmentType *) a).start > (*(SegmentType *) b).start) return(1);
- return(0);
- }
-
- /* comparison function for bsearch */
- static int search_compare(const void *key, const void *data)
- {
- if ((Ptr) key < (*(SegmentType *) data).start) return(-1);
- if ((Ptr) key >= (*(SegmentType *) data).end) return(1);
- return(0);
- }
-
- /* from THINK C's "bsearch.c" */
- static void *bsearch(const void *key, const void *base, size_t n, size_t size,
- int compare(const void *key, const void *data))
- {
- register size_t i = 0, j;
- void *guess;
- int k;
-
- while (i < n) {
- j = (i + n - 1) >> 1;
- guess = (char *) base + j * size;
- if ((k = compare(key, guess)) == 0)
- return(guess);
- if (k < 0)
- n = j;
- else
- i = j + 1;
- }
- return(NULL);
- }
-
- /* return the segment table */
- static SegmentType *SegmentTable(void)
- {
- static SegmentType *segments;
-
- if (! segments) {
- /* allocate segment table */
- short nseg = CountResources('CODE');
- FailResError();
- check(nseg > 0);
- segments = (SegmentType *) NewPtr(sizeof(SegmentType) * nseg);
- FailNIL(segments);
- }
- return(segments);
- }
-
- /* get addresses of loaded locked segments, return number of segments */
- static short SegmentsLoaded(SegmentType *segments)
- {
- Boolean load; /* saved ResLoad flag */
- short nloaded; /* number of loaded segments */
- short nseg; /* number of segments */
- short i; /* index to segment */
-
- /* get number of code resources */
- nseg = CountResources('CODE');
- FailResError();
- check(nseg > 0);
-
- check(GetPtrSize((Ptr) segments) == nseg * sizeof(SegmentType));
-
- /* Get addresses of all loaded segments. A loaded segment must not
- be purged and must be locked. These are segments which will need
- to call UnloadSeg for, but only if they don't contain any active code. */
- load = GetResLoad();
- SetResLoad(false);
- nloaded = 0;
- for (i = 1; i <= nseg; i++) {
- Handle code = GetIndResource('CODE', i);
- if (code && *code && (HGetState(code) & (1<<7)) != 0) {
- segments[nloaded].start = StripAddress(*code);
- segments[nloaded].end = StripAddress(*code) + SizeResource(code);
- segments[nloaded].active = false;
- nloaded++;
- }
- }
- SetResLoad(load);
- return(nloaded);
- }
-
- /* trace stack, marking segments containing return addresses */
- static void SegmentsActive(SegmentType *segments, short nloaded)
- {
- SegmentType *seg; /* segment found by bsearch() */
- Ptr stacktop; /* top of stack */
- Ptr stackbottom; /* bottom of stack */
- Ptr frame; /* stack frame pointer (starting from register a6) */
- Ptr retaddress; /* return address on stack */
- short i;
-
- /* sort list of loaded segments */
- qksort(segments, nloaded, sizeof(SegmentType), sort_compare);
-
- /* search list using binary search for each return address */
- stacktop = GetCurStackBase();
- asm { move.l sp, stackbottom }
- asm { move.l a6, frame }
- while (stackbottom <= frame && frame < stacktop) {
- retaddress = *((Ptr *) (frame + sizeof(Ptr)));
- seg = bsearch(retaddress, segments, nloaded,
- sizeof(SegmentType), search_compare);
- if (seg)
- seg->active = true;
- frame = *(Ptr *) frame;
- }
- if (frame) {
- /* Didn't trace entire stack, so something's wrong and
- we shouldn't unload any segments. This could occur,
- for instance, when called from certain ROM call-backs,
- such as a heap's grow zone function. This is unfortunate,
- since a heap grow zone function is the ideal place to
- unload segments, but the OS doesn't always use the stack
- frame convention based on register a6. */
- for (i = 0; i < nloaded; i++)
- segments[i].active = true;
- }
- }
-
-
- /* unload all inactive segments */
- static void SegmentsUnloadInactive(const SegmentType *segments, short nloaded)
- {
- short i;
-
- for (i = 0; i < nloaded; i++) {
- if (! segments[i].active)
- UnloadSeg(JTEntry(segments[i].start) + JT_ENTRY_HEADER_SIZE);
- }
- }
-
- /* enable/disable unloading of segments, return old value */
- Boolean SegmentsUnloadEnable(Boolean enable)
- {
- Boolean enabled;
-
- enabled = gUnloadEnable;
- gUnloadEnable = enable;
- return(enabled);
- }
-
- /* unload all inactive segments */
- void SegmentsUnload(void)
- {
- SegmentType *segments; /* table of segment addresses */
- short nloaded; /* number of loaded segments */
-
- if (gUnloadEnable) {
- segments = SegmentTable();
- nloaded = SegmentsLoaded(segments);
- SegmentsActive(segments, nloaded);
- SegmentsUnloadInactive(segments, nloaded);
- }
- }
-
- /* load the segment containing the function */
- void SegmentLoad(void *fun)
- {
- LoadSeg(JTNumber((Ptr) fun - JT_ENTRY_HEADER_SIZE));
- }
-
- /* initialize segments; this function must be in the main segment */
- void SegmentsInit(void)
- {
- (void) PatchBegin(PatchLoadSeg, _LoadSeg, sizeof(short), 0, NULL);
- (void) JTOffsetMultiplier(); /* initialize jump table entry multiplier */
- (void) SegmentTable(); /* allocate segment table */
- (void) SegmentsUnloadEnable(true);
- }
-